<template>
  <div id="lsit" :class="computedClass" ref="listWrapper" @scroll="handleScroll">
    <!--顶部插槽-->
    <slot name="top"></slot>
    <div class="loadMore text" ref="loadRef" v-if="!props.finished && props.loadPos==='top' && props.list.length > 0"><LoadingOutlined />加载更多
    </div>
    <!--item插槽遍历传过来的数据-->
    <slot v-bind="slotAttrs" v-for="(item, index) in props.list" :index="index" :item="item" name="item" :customClass="slotClass"></slot>
    <div class="over" ref="overRef">占位符，不得删除此文字</div>
    <!--通过视窗加载数据-->
    <div class="loadMore text" ref="loadRef" v-if="!props.finished && props.loadPos==='bottom'"><LoadingOutlined />加载中</div>
    <!--底部插槽-->
    <slot name="bottom"></slot>
  </div>
</template>
<!-- 该组件仅支持纵向排列 -->
<script lang="ts" setup>
import { ref, onMounted, computed, nextTick, watch, onUnmounted, onUpdated } from 'vue'
import { LoadingOutlined } from '@ant-design/icons-vue'

const emit = defineEmits(['loadData', 'visibleItemsChange', 'customScroll'])
const props = defineProps({
  finished: {
    type: Boolean,
    default: true
  },
  list: {
    type: Array,
    default: () => []
  },
  watch: {
    // 是否监听子集元素是否进入视窗
    type: Boolean,
    default: false
  },
  loadPos: {
    // 加载组件的位置，top | bottom 
    type: String,
    default: 'bottom'
  },
  customClass: {
    // 自定义class
    type: String,
    default: ''
  },
  scrollBottom: {
    // 是否初始化时滚轮滚到底部
    type: Boolean,
    default: false
  },
  loading: {
    // 是否正在加载数据
    type: Boolean,
    default: false
  }
})
const observer = ref<IntersectionObserver | null>(null) // 监测Laoding元素
const observerList = ref<IntersectionObserver | null>(null) // 监测子元素
const observerImgList = ref<MutationObserver | null>(null)
const loadRef = ref(null)
const overRef = ref(null)
const listWrapper = ref(null)
const visiableList = ref([]) // 当前位于视窗的列表项
const debounceTimeout = ref(null) // 防抖处理
const firstLoad = ref<Boolean>(true) // 是否第一次加载(监测子节点以及Loading加载都会用到)
const opacity = ref<number>(0) // 子元素是否透明
const totalImages = ref<number>(0) // 总图片数量
const imageLoadCount = ref<number>(0) // 已加载图片数量

// 计算属性处理自定义class
const computedClass = computed(()=> ['listWrapper scroller', props.customClass].join(' '),)
const slotClass = computed(()=>opacity.value === 0 ? "hideItem" : 'showItem')

// 监测Loading元素是否进入视窗
const setupIntersectionObserver = () => {
  observer.value = new IntersectionObserver(
    (entries) => {
      const entry = entries[0]
      // 由于Loading 在最上方时会自动触发视窗的效果， 所以这边判断Loading在最上方时第一次不触发loadData
      if (
        entry.isIntersecting && !props.finished && !firstLoad.value
      ) { 
        // 记录当前滚轮位置
        const scrollPosition = listWrapper.value.scrollHeight;
        // 出现在视窗之内 加载数据
          emit('loadData')
          if(props.loadPos==='top') {
            // 浏览器对屏幕刷新一次就执行一次
            requestAnimationFrame(()=> {
                listWrapper.value?.scrollTo({
                  top: listWrapper.value.scrollHeight - scrollPosition,
                  behavior: 'instant'
                })
            })
          }
      } else {
        firstLoad.value = false
      }
    },
    {
      root: null, //默认为视窗
      rootMargin: '0px',
      threshold: [1] // 当元素0%或100%可见时触发回调
    }
  )
  // 此处观察LoadingRef元素是否进入视窗
  loadRef.value && observer.value.observe(loadRef.value)
}
// 监测List中的子集元素是否进入视窗
const setupIntersectionListObserver = () => {
  if(observerList.value) observerList.value = null
  observerList.value = new IntersectionObserver(
    (entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          // 进入视窗
          if (visiableList.value.every((item) => item !== entry.target.dataset.id)) {
            visiableList.value.push(entry.target.dataset.id)
          }
        } else {
          // 离开视窗
          visiableList.value = visiableList.value.filter((item) => item !== entry.target.dataset.id)
        }
      })
      // 第一次进入直接返回视窗内的数据
      if (firstLoad.value) {
        firstLoad.value = false
        emit('visibleItemsChange', visiableList.value)
      }
    },
    {
      root: null, //默认为视窗
      rootMargin: '0px',
      threshold: [0.2, 1] // 当元素0%或100%可见时触发回调
    }
  )
  // 观察所有的子节点
  const items = listWrapper.value.querySelectorAll('.list-item')
  items.forEach((item) => observerList.value.observe(item))
}
// 滚轮事件进行防抖处理
const handleScroll = () => {
  emit('customScroll', {})
  if (debounceTimeout.value) {
    clearTimeout(debounceTimeout.value)
  }
  debounceTimeout.value = setTimeout(() => {
    emit('visibleItemsChange', visiableList.value)
  }, 200) // 500ms 防抖延迟
}

// 滚动到最下方
const scrollToBottom =  () =>{
  if(listWrapper.value.scrollHeight) {
    nextTick(()=> {
      // overRef.value && overRef.value?.scrollIntoView({ behavior: 'instant' })
      listWrapper.value?.scrollTo({
          top: listWrapper.value.scrollHeight,
          behavior: 'instant'
      })
      setTimeout(()=> {
        opacity.value = 1
        observerImgList.value?.disconnect()
      }, 0)
    })
  }
}
const observeImages = () =>{
  observerImgList.value = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
      if (mutation.type === 'childList') {
        totalImages.value = listWrapper.value.querySelectorAll('img').length;
        imageLoadCount.value = 0;
        listWrapper.value.querySelectorAll('img').forEach((img) => {
          img.addEventListener('load', onImageLoad);
        });
      }
    });
  });
  observerImgList.value.observe(listWrapper.value, {
    childList: true,
    subtree: true
  });
}
const onImageLoad = () =>{
  imageLoadCount.value++;
  if (imageLoadCount.value === totalImages.value) {
    scrollToBottom();
  }
}
// 监测子节点发生变化时重新监控数据
watch(()=> props.list, (v1, v2)=> {
    // 如果数据发生变化 并且props设置的是监测子组件
    if(props.watch) {
      setupIntersectionListObserver()
    }
    // 初次渲染时滚动滚轮到最下方（放到onMounted里面会因为渲染不及时的问题导致不滚动）
    if(v2.length == 0 && v1.length > 0 && props.scrollBottom) {
      observeImages()
    }
  }
)
// 监测load元素隐藏后再次显示
watch(()=> props.finished, (v1, v2)=> {
    if(!v1 && v2) {
      // 元素渲染之后重新监控Loading元素
      nextTick(()=> {
        // 监测Loading
        setupIntersectionObserver()
      })
    }
  }
)
onUpdated(()=>  {
})
onMounted(() => {
  console.log('初始化')
  if (imageLoadCount.value === totalImages.value && props.loadPos === 'top') {
    scrollToBottom()
    opacity.value = 1
  }
  // 监测Loading
  setupIntersectionObserver()
  // 是否监测所有的子组件
  if (props.watch) {
    setupIntersectionListObserver()
  }
})
onUnmounted(()=> {
  console.log('list组件已经销毁, 正在清除监听')
  observer.value && observer.value.disconnect() // 解绑监测Laoding元素
  observerList.value && observerList.value.disconnect() // 解绑监测子元素
  observerImgList.value && observerImgList.value.disconnect() // 解绑监测图片元素
})
// 暴漏方法给父组件
defineExpose({
  scrollToBottom
})
</script>
<style lang="less" scoped>
@import '@/assets/base.less';
.listWrapper {
  height: 100%;
  width: 100%;
  overflow-y: scroll;
  display: flex;
  flex-direction: column;

  .loadMore {
    width: 100%;
    text-align: center;
    height: 60px;
    line-height: 60px;
    gap: 10px;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: #e6e6e6;
    color: #c1c0c0;
    &.text {
      color: #4949ab;
      background: unset;
      height: 30px;
      line-height: 30px;
      font-size: 15px;
      margin-bottom: 20px;
    }
  }
  .over {
    width: 100%;
    height: 10px;
    color: transparent;
  }
  .noMore {
    width: 100%;
    text-align: center;
    height: 60px;
    line-height: 60px;
    background-color: #e6e6e6;
    color: #c1c0c0;
  }
}
</style>
<style lang="less">
  .hideItem {
    opacity: 0 !important;
  }
  .showItem {
    opacity: 1 !important;
  }
</style>
